/*------------------------------------------------------------------- 实验平台: 清翔电子 QX - X3平台 功能描述: 实时时钟,LCD显示,通过按键可调节参数 使用说明: 1、KEY5用做功能选择:KEY5按下1次,调整日参数;按两次,调月;3次,调年;4次,调分; 5次,调时;6次,关光标及其闪烁 2、KEY6用做数值累加:每按一下,当前值加1 3、KEY7用做数值递减:每按一下,当前值减1 注意事项: 按键增减功能在BST-V51平台有效,但在QX-X3平台有错误 原因分析:LCD引脚与按键冲突; 理由:LCD的RS/RW/LCDEN的接线与矩阵键盘前3排按键接口引脚相同,故而LCD写数据出错。 作 者: 黄建雄 日 期: 2016.04.07 -------------------------------------------------------------------*/ ;-------------------------------------------------------------------- RS EQU P3.5 ; 引脚声明 RW EQU P3.6 E EQU P3.4 LCD EQU P0 ; P0为数据端 BF EQU P0.7 SECOND EQU 60H ; 时间存储区域首地址 SelFuc EQU 20H ; 功能选择键按下标志 POS EQU 21H T_CLK BIT P2.5 ; 定义DS1302时钟线引脚 T_IO BIT P2.4 ; 定义DS1302数据线引脚 T_RST BIT P2.3 ; 定义DS1302复位线引脚 ORG 0000H LJMP START ; 跳向主程序 ORG 0100H ;-------------------------------------------------------------------- START: MOV A, #038H ; 向LCD写入“显示模式设置”命令38H LCALL W_CMD ; 写入命令 MOV A, #0CH ; 显示控制指令:开显示,不显示光标 LCALL W_CMD ; 写入命令 MOV P0, #0FFH ; 关闭数码管全部位选 SETB P2.7 CLR P2.7 MOV SP, #30H ; 重新设置堆栈指针 MOV POS, #95H ; 95H单元用来存储光标的位置 LOOP: MOV A, #00H MOV R0, #60H MOV R2, #32 CLR_BUF: MOV @R0, A ; 片内首地址为60H的32个RAM显示缓冲单元清0 INC R0 DJNZ R2, CLR_BUF MOV 60H, #0H ; 这里末尾加H只是为了便于后续显示处理,并不用做16进制值 MOV 61H, #59H MOV 62H, #23H MOV 63H, #4H MOV 64H, #4H MOV 65H, #1H MOV 66H, #16H MOV 67H, #20H LCALL SET1302 ; 调用设置DS1302的子程序 MOV DPTR, #200 LCALL WTMS // 调用延时子程序 延时时间:200 / 2 * 100us = 10ms LOOP2: NOP LCALL GET1302 ; 获取时钟数据 MOV A, #01H ; 清屏指令 LCALL W_CMD ; 写入命令 01H LCALL WRTIME ; 调用向LCD写入时间子程序 MOV A, POS ; 光标定位 ACALL W_CMD MOV DPTR, #800 ; DPTR赋值为800 MOV A, #1 ; A赋值为1 这是时间刷新的频率参数 LCALL WTSEC ; 调用延时程序 LJMP KEY2 JMP LOOP2 // 重新回到循环 ;--------------------------------------------------------------------- WRTIME: MOV A, #80H ; LCD第一行显示定位为06H,即86H = 80 + 06H ACALL W_CMD WRSTR1: CLR A ; 清空累加器 MOV A, SECOND + 2 ; 把时钟数据存储空间的地址赋给A MOV B, #10H DIV AB ADD A, #30H ACALL W_DATA ; LCD写数据 小时高位 MOV A, B ADD A, #30H ACALL W_DATA ; LCD写数据 小时低位 MOV A, #2DH ; 显示字符“-” ACALL W_DATA MOV A, #2DH ; 显示字符“-” ACALL W_DATA MOV A, SECOND + 1 ; 把分钟数据存储空间的地址赋给A MOV B, #10H DIV AB ADD A, #30H ACALL W_DATA ; LCD写数据 小时高位 MOV A, B ADD A, #30H ACALL W_DATA MOV A, #2DH ; 字符“-” ACALL W_DATA MOV A, #2DH ; 显示字符“-” ACALL W_DATA MOV A, SECOND ; 把秒钟数据存储空间的地址赋给A MOV B, #10H DIV AB ADD A, #30H ACALL W_DATA ; LCD写数据 小时高位 MOV A, B ADD A, #30H ACALL W_DATA ; 秒钟低位 MOV A, #90H ; 第二行定位 ACALL W_CMD MOV A, SECOND + 7 ; 把年份数据存储空间的地址赋给A MOV B, #10H DIV AB ADD A, #30H ACALL W_DATA ; LCD写数据 小时高位 MOV A, B ADD A, #30H ACALL W_DATA MOV A, SECOND + 6 ; 把年份数据存储空间的地址赋给A MOV B, #10H DIV AB ADD A, #30H ACALL W_DATA ; LCD写数据 小时高位 MOV A, B ADD A, #30H ACALL W_DATA MOV A, #2DH ; 字符“-” ACALL W_DATA MOV A, #2DH ; 显示字符“-” ACALL W_DATA MOV A, SECOND + 4 ; 把月份数据存储空间的地址赋给A MOV B, #10H DIV AB ADD A, #30H ACALL W_DATA ; LCD写数据 小时高位 MOV A, B ADD A, #30H ACALL W_DATA MOV A, #2DH ; 字符“-” ACALL W_DATA MOV A, #2DH ; 显示字符“-” ACALL W_DATA MOV A, SECOND + 3 ; 把日期数据存储空间的地址赋给A MOV B, #10H DIV AB ADD A, #30H ACALL W_DATA ; LCD写数据 小时高位 MOV A, B ADD A, #30H ACALL W_DATA WRSTR2: RET ;------------------------------------------------------------ WTSEC: PUSH ACC ; 等待(A)秒 LCALL WTMS ; 延时40ms POP ACC DEC A JNZ WTSEC ; 累加器不为0则转移 RET ;------------------------------------------------------------ ; 延时等待(DPTR)毫秒 WTMS: XRL DPL, #0FFH ; 按位异或 XRL DPH, #0FFH INC DPTR WTMS1: MOV TL0, #09CH MOV TH0, #0FFH ; 计数周期为100,12M时钟下为100us MOV TMOD, #1 ; 定时器T0方式1 SETB TR0 ; 定时器T0运行 WTMS2: JNB TF0, WTMS2 ; 判TF0的状态,TF0 = 0即未溢出时,则原地跳转等待 CLR TR0 ; TF0 = 1, 定时器T0停止 CLR TF0 ; 清TF0 INC DPTR MOV A, DPL ORL A, DPH ; DPTR加到65535后,再加就会为0,即DPH = DPL = 0,此时便可退出循环 JNZ WTMS1 ; 累加器不为0则转移 RET /*------------------------------------------------------------------------------ ; 调 用:RTINPUTBYTE ; 入口参数:初始时间在:SECOND, MINUTE, HOUR, DAY, MONTH, WEEK, YEARL(地址连续) ;------------------------------------------------------------------------------*/ SET1302:CLR T_RST ; 设置DS1302初始时间,并启动计时。RST = 0 CLR T_CLK ; SCLK = 0(RST置为1时,SCLK必须为0) SETB T_RST ; RST = 1 MOV B, #8EH ; 写保护寄存器写入命令8EH LCALL RTINPUTBYTE ; 调用向DS1302写入一字节子程序 MOV B, #00H ; 写操作前WP = 0 LCALL RTINPUTBYTE ; 调用向DS1302写入一字节子程序 SETB T_CLK CLR T_RST MOV R0, #SECOND ; 秒送入R0 MOV R7, #7 ; 秒、分、时、日、月、星期、年 MOV R1, #80H ; 80H为秒寄存器“写”命令 S13021: CLR T_RST CLR T_CLK SETB T_RST MOV B, R1 ; 写秒寄存器写入命令 LCALL RTINPUTBYTE ; 向1302写入命令字 MOV A, @R0 ; 写入秒数据 MOV B, A LCALL RTINPUTBYTE ; 向1302写入一字节 INC R0 ; 存储地址增1 INC R1 INC R1 ; R1内容增2,即分寄存器“写”命令82H,后面依次为时、日...寄存器“写”命令 SETB T_CLK CLR T_RST DJNZ R7, S13021 ; 循环,直到7个寄存器全部被初始化 /* 写保护配置 */ CLR T_RST ; RST置0 CLR T_CLK ; SCLK置0 SETB T_RST ; RST置1 MOV B, #8EH ; 写保护寄存器“写”命令 LCALL RTINPUTBYTE ; 写入一字节 MOV B, #80H ; 控制, WP = 1, 写保护 LCALL RTINPUTBYTE ; 写入一字节 SETB T_CLK CLR T_RST RET ;-------------------------------------------------------------------- GET1302:MOV R0, #SECOND ; 从DS1302读时间/日历子程序,时间/日历保存在: ; SECOND, MINUTE, HOUR, DAY, MONTH, WEEK, YEARL中 MOV R7, #7 MOV R1, #81H ; 秒寄存器的“读”命令81H G13021: CLR T_RST CLR T_CLK SETB T_RST MOV B, R1 LCALL RTINPUTBYTE ; 先写 命令字 LCALL RTOUTPUTBYTE ; 再读出相应地址存储的数据 MOV @R0, A ; 取出数据放在R0指向的地址 INC R0 ; 存储地址值增1 INC R1 INC R1 ; R1内容加2,即81H + 2 = 83H(分寄存器读命令) SETB T_CLK CLR T_RST DJNZ R7, G13021 ; 未读完7个寄存器,继续 RET RTINPUTBYTE: ; 串行写入DS1302一字节子程序 MOV R4, #8 ; R4用做计数器 INBIT1: MOV A, B ; 串行移入8位数据 RRC A ; A的内容带进位位循环右移 MOV B, A ; 这里B用做数据中间值保存 MOV T_IO, C ; 数据通过DS1302的I/O脚串行移入DS1302 SETB T_CLK ; 拉高时钟 CLR T_CLK ; 拉低时钟 手册上规定时钟高电电平最长保持时间1000ns = 1us,CLR指令刚好1us DJNZ R4, INBIT1 ; 8次移位未完成,则跳INBIT1处继续 RET ; 子程序返回 RTOUTPUTBYTE: ; 从DS1302串行输出一字节子程序 MOV R4, #8 ; 计数器 OUTBIT1:MOV C, T_IO ; DS1302的I/O脚串行移出数据 RRC A ; A的内容带进位位循环右移 SETB T_CLK ; 拉高时钟 CLR T_CLK ; 拉低时钟 DJNZ R4, OUTBIT1 ; 8次移位未完成,则跳INBIT1处继续 RET ; 子程序返回 /***************************************************************************/ KEY2: LCALL KS ;键盘检测 JNZ K1 ;累加器不为0表可能有键按下,跳至K1再次检测 LCALL D10MS ;书上写了要去陡,但好像这里去不去陡没太大关系,不去也能行 AJMP LOOP2 ;无键按下返回重新检测 K1: LCALL D10MS ;去陡,再次检测 LCALL KS JNZ K2 AJMP LOOP2 ;无键按下返回重新检测 K2: MOV R2, #0EFH ;每次只有一列电平置低,即每次只扫描一列 MOV R4, #00H ;列号,下同 K3: MOV P3, R2 ;开始扫描 L0: JB P3.0, L1 ;端口值为高则说明此列无键按下,跳到下一端口检测程序 MOV A, #00H ;行首键号送累加器 AJMP LK L1: JB P3.1, L2 MOV A, #04H AJMP LK L2: JB P3.2, L3 MOV A, #08H AJMP LK L3: JB P3.3, NEXT ;端口值为高则说明此列无键按下,准备扫描下一列 MOV A, #0CH LK: ADD A, R4 ;行的首键号+列号算出键值 PUSH ACC ;因后面还要用到ACC,所以读出键值后需要把ACC入栈保存 NEXT: INC R4 MOV A, R2 JNB ACC.7, K4 ;判是否扫描完,完则跳转到松手检测 RL A ;左环移,扫描下一列应送的值 MOV R2, A AJMP K3 ;未扫描完跳至K3再次扫描 K4: LCALL KS ;键盘检测,判松手 JNZ K4 ;未松开则原地等待 POP ACC ;累加器出栈 LJMP START2 ;出栈后跳至显示程序 KS: MOV P3, #0FH ;键盘检测,有键按下非0 MOV A, P3 XRL A, #0FH ;异或指令,判有无键按下,聪明呀! RET START2: MOV DPTR, #TABLE ;把管码表首地址给数据指针 ;数码管段位选初始化 CLR P2.6 CLR P2.7 FUNCTION: CJNE A, #1, INCREASE INC SelFuc MOV A, SelFuc // 把KEY5按下次数值赋给A CJNE A, #1, CASE1 MOV A, #0FH ; 若KEY1第1次按下,则设置显示光标、光标闪烁 LCALL W_CMD MOV POS, #0X95 LJMP DISPLAY CASE1: CJNE A, #2, CASE2 MOV POS, #0X93 LJMP DISPLAY CASE2: CJNE A, #3, CASE3 MOV POS, #0X91 LJMP DISPLAY CASE3: CJNE A, #4, CASE4 MOV POS, #0X82 LJMP DISPLAY CASE4: CJNE A, #5, CASE5 MOV POS, #0X80 LJMP DISPLAY CASE5: CJNE A, #6, DISPLAY // 若KEY5按下,则执行完坐标处理后就跳出本轮按键判断 MOV A, #0CH ; 若KEY5第6次按下,则关闭光标 LCALL W_CMD MOV SelFuc, #0 ; 计数值最大只能到6,超过则清0 LJMP DISPLAY ;---------------------------------------------------------------- INCREASE: CJNE A, #5, DECREASE MOV A, SelFuc CJNE A, #1, INC1 INC 63H LJMP SETINC MOV A, 63H CJNE A, #30, INC2 ADD A, #33 MOV 63H, A INC1: CJNE A, #2, INC2 INC 64H LJMP SETINC INC2: CJNE A, #3, INC3 INC 66H LJMP SETINC INC3: CJNE A, #4, INC4 INC 61H LJMP SETINC INC4: CJNE A, #5, DISPLAY INC 62H SETINC: LCALL SET1302 LJMP DISPLAY ;----------------------------------------------------------------- DECREASE: CJNE A, #9, DISPLAY MOV A, SelFuc CJNE A, #1, DEC1 DEC 63H LJMP SETDEC DEC1: CJNE A, #2, DEC2 DEC 64H LJMP SETDEC DEC2: CJNE A, #3, DEC3 DEC 66H LJMP SETDEC DEC3: CJNE A, #4, DEC4 DEC 61H LJMP SETDEC DEC4: CJNE A, #5, DISPLAY DEC 62H SETDEC: LCALL SET1302 ;---------------------------------------------------------------- DISPLAY: MOVC A, @A+DPTR SETB P2.7 ;开位选 MOV P0, #0FEH CLR P2.7 MOV P0, #00H ;消影 SETB P2.6 ;开段选 MOV P0, A CLR P2.6 ;锁存 LJMP LOOP2 ;显示完后跳至检测程序继续检测 TABLE: DB 3FH, 06H, 5BH, 4FH, 66H ;共阴数码管码表 DB 6DH, 7DH, 07H, 7FH, 6FH DB 77H, 7CH, 39H, 5EH, 79H DB 71H D10MS: MOV R7, #25 ;延时10MS D1: MOV R6, #200 DJNZ R6, $ ;美元符号表示原地跳转 DJNZ R7, D1 RET ;----------------------------------------------------------- ; WAIT子程序用于检测1602是否处于忙状态,直到1602空闲时才退出 WAIT: MOV LCD, #0FFH CLR RS SETB RW CLR E NOP SETB E JB BF, WAIT RET ; 写入命令子程序,入口参数A中存储了向1602写入的命令 W_CMD: ACALL WAIT MOV LCD, A CLR RS ; RS与RW清0 CLR RW SETB E ; 令E端产生下降沿,命令写入1602 NOP CLR E RET ; 写入数据子程序,入口参数A中存储了向1602写入的数据 W_DATA: ACALL WAIT MOV LCD, A SETB RS ; 将RS置 1 CLR RW ; 将RW清 0 SETB E ; 令E端产生下降沿,数据写入1602 NOP CLR E RET ;------------------------------------------------------------ END ; 结束